/*
 * @Author: lirl
 * @Date: 2022-07-18 16:50:27
 * @Description: 表单校验方法
 */
import dayjs from 'dayjs';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import XRegExp from 'xregexp';
import { document } from 'globalthis/implementation';
import {
  isNil,
  mapKeys,
  flatMap,
  isPlainObject
} from 'lodash-es';
import { message } from '@/api/utils/pc-message';

dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);

// 百分数校验规则
const PERCENT_RULE = XRegExp('^(\\d{1,2}(\\.[0-9]{1})?)$|^100$');
// 正整数校验规则
const INTEGER = XRegExp('^[1-9]\\d*$');
// 全部是数字
const ISALLNUMBER = XRegExp('^\\d+$');
// 最多两位小数的数字
const TWO_DECIMAL_PLACES = XRegExp('^\\d+(\\.\\d{0,2})?$', 'g');
// 最多六位小数的数字
const SIX_DECIMAL_PLACES = XRegExp('^\\d+(\\.\\d{0,6})?$', 'g');
// 科学计数法
const NUMBER_LENGTH_REGEXP = XRegExp('^\\d{1,8}(\\.\\d+)?$','g');

// 手机号(mobile phone)中国(严谨), 根据工信部2019年最新公布的手机号段"
const PHONE_NUMBER = XRegExp(
  '^(?:(?:\\+|00)86)?1[3-9]\\d{9}$'
);

// 校验数字或数字区间
const NUMBER_RANGE = XRegExp('^(\\d+-)?\\d+$');

/**
 * tinymce获取焦点
 * @param {object} tinymceObj tinymce dom对象
 * @param {object} ifrWindow tinymce iframe的window对象
 */
const tinymceFocus = (tinymceObj, ifrWindow) => {
  if (ifrWindow.getSelection) {
    tinymceObj.focus();
    const range = ifrWindow.getSelection();
    range.selectAllChildren(tinymceObj);
    range.collapseToEnd();
  }
};

/**
 * element-ui表单校验不通过时，获取焦点
 */
const domFocusError = () => {
  const [error] = document.getElementsByClassName('is-error');
  if (error) {
    // 先校验其他基础dom
    const selector = error.querySelector('input,textarea,.el-upload');
    selector && selector.focus();
    // 校验tinymce富文本
    const ifr = error.querySelector('iframe');
    if (ifr) {
      // 1. 使富文本获取焦点
      tinymceFocus(ifr.contentWindow.document.getElementById('tinymce'), ifr.contentWindow);
      // 2. 使滚动条滚动到富文本处
      // 获取iframe元素距可视窗口顶部的距离
      const clientTop = error.querySelector('.tox-tinymce').getBoundingClientRect().top;
      // 获取滚动条在Y轴滚动距离
      const scrollY = document.documentElement.scrollTop;
      // 需要滚动的距离 = 滚动条已滚动的距离 + 元素到可视窗口顶部的距离
      document.documentElement.scrollTop = clientTop + scrollY;
    }
  }
};

/**
 * 表单校验
 * @param {Object} form 表单对象
 */
const validateForm = (form, focusError = true) =>
  new Promise((resolve) => {
    if (!form) {
      resolve();
    }
    form.validate((valid) => {
      if (valid) {
        resolve();
      } else if (focusError) {
        setTimeout(domFocusError, 100);
      }
    });
  });

/**
 * 表单校验,以弹窗的方式
 * @param {Object} form 表单对象
 */
const validateFormByAlert = (form, focusError = true) =>
  new Promise((resolve) => {
    if (!form) {
      resolve();
    }
    form.validate((valid, object) => {
      if (valid) {
        resolve();
      } else if (focusError) {
        const messageErrorTip = [];
        mapKeys(object, (v) => {
          messageErrorTip.push(v);
        });
        const [{ message: tip }] = flatMap(messageErrorTip);
        // 弹窗并且定位
        message(tip, 'warning', 3000, true, 'pj-validate');
        setTimeout(domFocusError, 100);
      }
    });
  });

/**
 * 表单校验
 * @param {Object} form 表单对象
 * @param {array | string} fields 需要校验的prop
 */
const validateField = (form, fields) =>
  new Promise((resolve) => {
    form?.validateField(fields, (valid) => {
      !valid && resolve();
    });
  });

/**
 * 校验两个时间
 * @param {String} compareType 对比类型 isBefore、isAfter、isSameOrBefore、isSameOrAfter
 * @param {String} compareValue 要对比的值
 * @param {String} msg 提示信息
 */
const validateTime = (compareType, compareValue, msg) => (value, callback) => {
  if (dayjs(value)[compareType](dayjs(compareValue))) {
    callback(new Error(msg));
  }
  callback();
};

/**
 * 校验小于等于100，最多一位小数的数字
 * @param {String} rule 规则
 * @param {String} value 值
 * @param {Function} callback 回调
 */
const validatePercent = (rule, value, callback) => {
  PERCENT_RULE.lastIndex = 0;
  if (!value || PERCENT_RULE.test(value)) {
    callback();
  } else {
    callback(new Error('请输入小于等于100，最多1位小数的数字'));
  }
};

/**
 * 校验正整数
 * @param {String} rule 规则
 * @param {String} value 值
 * @param {Function} callback 回调
 */
const validateInteger = (rule, value, callback) => {
  INTEGER.lastIndex = 0;
  if (!value || INTEGER.test(value)) {
    callback();
  } else {
    callback(new Error('请输入大于0的整数'));
  }
};

/**
 * 校验字符串是否全由数字组成
 * @param {String} value 要校验的字符串
 * @param {String} msg 提示信息
 */
const checkAllNumberStr = (value) => {
  ISALLNUMBER.lastIndex = 0;
  return value && ISALLNUMBER.test(value);
};

/**
 * 校验最多两位小数的数字
 * @param {Object} rule 校验规则
 * @param {String} value 要校验的字符串
 * @param {Function} callback 校验后执行的方法
 */
const validateTwoDecimalNum = (rule, value, callback) => {
  TWO_DECIMAL_PLACES.lastIndex = 0;
  if (!value || TWO_DECIMAL_PLACES.test(value)) {
    callback();
  } else {
    callback(new Error('请输入最多两位小数的数字'));
  }
};

/**
 * 校验最多六位小数的数字
 * @param {Object} rule 校验规则
 * @param {String} value 要校验的字符串
 * @param {Function} callback 校验后执行的方法
 */
const validateSixDecimalNum = (rule, value, callback) => {
  SIX_DECIMAL_PLACES.lastIndex = 0;
  if (!value || SIX_DECIMAL_PLACES.test(value)) {
    callback();
  } else {
    callback(new Error('请输入最多六位小数的数字'));
  }
};

/**
 * 校验json格式
 * @param {String} rule 规则
 * @param {String} value 值
 * @param {Function} callback 回调
 */
const validateJson = (rule, value, callback) => {
  try {
    const json = JSON.parse(value);
    isPlainObject(json) ? callback() : callback(new Error('请输入正确的json格式'));
  } catch (error) {
    callback(new Error('请输入正确的json格式'));
  }
};

/**
 * 校验数字大于0
 * @param {*} rule
 * @param {*} value
 * @param {*} callback
 */
const validateGreaterThanZero = (rule, value, callback) => {
  if (!isNil(value) && value <= 0) {
    callback(new Error('请输入大于0的数字'));
  }
  callback();
};

/**
 * 校验输入科学计数法
 * @param {*} rule
 * @param {*} value
 * @param {*} callback
 */
const validateNumberFormat = (rule, value, callback) => {
  NUMBER_LENGTH_REGEXP.lastIndex = 0;
  if (isNil(value) || NUMBER_LENGTH_REGEXP.test(value)) {
    callback();
  } else {
    callback(new Error('请输入正确格式的，整数部分最多8位的数字'));
  }
};

/**
 * 校验手机号
 * @param {*} rule
 * @param {*} value
 * @param {*} callback
 */
const validatePhoneNumber = (rule, value, callback) => {
  PHONE_NUMBER.lastIndex = 0;
  if (isNil(value) || PHONE_NUMBER.test(value)) {
    callback();
  } else {
    callback(new Error('请输入正确格式的手机号'));
  }
};

// 校验数字或数字区间
const validateNumberRange = (rule, value, callback) => {
  if (value && !NUMBER_RANGE.test(value)) {
    callback(new Error('请输入数字或者数字区间'));
  } else {
    callback();
  }
};

export {
  validateForm,
  validateField,
  validateFormByAlert,
  validateTime,
  validatePercent,
  validateInteger,
  checkAllNumberStr,
  validateTwoDecimalNum,
  validateSixDecimalNum,
  validateJson,
  validateGreaterThanZero,
  validateNumberFormat,
  validatePhoneNumber,
  validateNumberRange
};
